package cn.sciento.core.interceptor;

import cn.sciento.core.exception.NotFoundException;
import com.google.common.collect.ImmutableMap;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

public abstract class AbstractInterceptorChainManager<T> {
  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInterceptorChainManager.class);
  
  private final Map<ChainId, InterceptorChain<T>> chainMap;
  
  public AbstractInterceptorChainManager(List<HandlerInterceptor<T>> interceptorList, List<InterceptorChainConfigurer<T, InterceptorChainBuilder<T>>> configurerList) {
    this.chainMap = ImmutableMap.copyOf(initInterceptorChain(interceptorList, configurerList));
    LOGGER.info("Register {} InterceptorChain, names are [{}]", this.chainMap.size(), this.chainMap.keySet().stream().map(ChainId::id).collect(Collectors.joining(",")));
  }
  
  private Map<ChainId, InterceptorChain<T>> initInterceptorChain(List<HandlerInterceptor<T>> interceptorList, List<InterceptorChainConfigurer<T, InterceptorChainBuilder<T>>> configurerList) {
    if (CollectionUtils.isEmpty(interceptorList)) {
      throw new IllegalArgumentException("Interceptors is empty.");
    }
    if (CollectionUtils.isEmpty(configurerList)) {
      throw new IllegalArgumentException("Interceptor configurers is empty.");
    }
    InterceptorChainBuilder<T> builder = new InterceptorChainBuilder<>(interceptorList);
    configurerList.sort(AnnotationAwareOrderComparator.INSTANCE);
    configurerList.forEach(configurer -> {
          configurer.configure(builder);
          builder.clearChain();
        });
    List<InterceptorChain<T>> chains = builder.performBuild();
    return chains.stream().collect(Collectors.toMap(InterceptorChain::getChainId, Function.identity()));
  }
  
  public final void doInterceptor(ChainId chainId, T target, Operation<T> operation) {
    InterceptorChain<T> chain = this.chainMap.get(chainId);
    if (chain == null) {
      throw new NotFoundException("InterceptorChain Not Found For [" + chainId.id() + "]");
    }
    chain.doExecute(target, operation);
  }
  
  public final void doInterceptor(ChainId chainId, T target) {
    InterceptorChain<T> chain = this.chainMap.get(chainId);
    if (chain == null) {
      throw new NotFoundException("InterceptorChain Not Found For [" + chainId.id() + "]");
    }
    chain.doExecute(target, null);
  }
}
